#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libgen.h>
#include <errno.h>
#include <assert.h>
#include <stdint.h>

// Change DEBUG to 1 to see debug messages.
#define DEBUG 0

#define DEBUGLOG(...) if (DEBUG) printf(__VA_ARGS__)

int64_t processed = 0; // How many files have been processed.
int cycle = 0; // If cycle == 0, we want to print information on the file we are processing.
int fullcycle = 1; // Set at start of program, one out of every fullcycle files will have information printed.
int64_t maxprocess = 1; // Set at start of program, to limit how many files should be processed.

int64_t totalsize = 0; // Total size of all files that have been processed, added together.
int64_t totalwasted[6] = {0,0,0,0,0,0}; // Accumulated wasted space from all files processed so far, for each block size.

// Returns 1 if directory should be ignored, 0 othervise.
// This is made to work at the lab computers.
int ignored(char *path) {
	if (!strcmp(basename(path), ".")) return 1;
	if (!strcmp(basename(path), "..")) return 1;
	if (!strncmp(path, "/dev", 4)) return 1;
	if (!strncmp(path, "/proc", 4)) return 1;
	if (!strncmp(path, "/sys", 4)) return 1;
	if (!strncmp(path, "/temporary", 10)) return 1;
	if (!strncmp(path, "/chalmers", 9)) return 1;
	if (!strncmp(path, "/nfs4", 5)) return 1;
	if (!strncmp(path, "/coda", 5)) return 1;
	if (!strncmp(path, "/var/lib/nfs", 12)) return 1;
	if (!strncmp(path, "/cab", 4)) return 1;
	return 0;
}

// Assembles a full path name for a file, given a directory name and a file name.
char *makepath(char *buf, size_t size, char *dir, char *file) {
	strncpy(buf, dir, size);
	if (strcmp(dir, "/"))
		strcat(buf, "/");
	strncat(buf, file, 256);
	return buf;
}

// This function is called once for each file that should be processed, with the full path of the file as a parameter
void scanfile(char *name) {
	DEBUGLOG("Starting processing of file: %s\n", name);
	
	// process file
	struct stat *stat = malloc(sizeof(struct stat));
	if (lstat(name, stat) != 0) {
		if (errno == EACCES) {
			free(stat);
			return;
		}
		perror("lstat()");
		exit(1);
	}
	int size = stat->st_size;
	totalsize += size;
	if (cycle == 0) {
		printf("%s\n", name);
	}
	int j = 0;
	int i = 0;
	for (i = 512; i <= 16384 ; i = i * 2) {
		int blocksize = (size / i ) + 1;
		int usedsize = blocksize * i;
		assert(usedsize > size);
		int waste = usedsize - size;
		totalwasted[j] += waste;
		if (cycle == 0) {
			// write information
			printf("%10d", waste);
			if (i < 16384) printf("|");
			else printf("\n");
		}
		j++;
	}

	free(stat);

	if (++processed == maxprocess) exit(0);
	if (++cycle == fullcycle) cycle = 0;
}

// Searches through a directory, calling searchdir() for any directory it finds, and scanfile() for every normal file.
void searchdir(char *name) {
	DEBUGLOG("Starting search of directory: %s\n", name);
	DIR *dir = opendir(name);
	if (dir == NULL) {
		if (errno == EACCES) return;
		else {
			perror("opendir()");
			exit(1);
		}
	}
	struct dirent *ent = NULL;
	while (ent = readdir(dir)) {
		if (ent->d_type == DT_DIR) {
			// ent is a directory
			char path[1024];
			makepath(path, sizeof(path), name, ent->d_name);
			if (!ignored(path))
				searchdir(path);
		}
		if (ent->d_type == DT_REG) {
			// ent is a regular file
			char path[1024];
			makepath(path, sizeof(path), name, ent->d_name);
			if (!ignored(path))
				scanfile(path);
		}
	}
	closedir(dir);
}

int main(int argc, char **argv, char **envp) {
	if (argc != 3) return 1;
	maxprocess = atoi(argv[1]);
	fullcycle = atoi(argv[2]);
	DEBUGLOG("Variables initialized, starting search\n");
	searchdir("/");
	
	printf("\n\n\n");
	printf("Number of files: %d\n\n", processed);
	int i = 0;
	int j = 0;
	for (i = 512; i <= 16384; i = i*2) {
		printf("Blocksize: %10d | Data waste: %12.5g | Inode waste: %12.5g | Total waste: %12.5g\n",
		i,  (double)totalwasted[j]/(double)totalsize, ((((processed*128)/i)+1)*i)/(double)totalsize, (((((processed*128)/i)+1)*i)+totalwasted[j])/(double)totalsize
		);
		DEBUGLOG("	Actual data waste: %lld\n", totalwasted[j]);
		j++;
	}

	return 1;
}
